/*
 * jQuery Validator Plugin v1.1
 *
 * Copyright (c) 2009 Rafael Cesar
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Date: 2009-01-16 00:26:00 -0300 (Fri, 16 Jan 2009)
 */
(function($) {
	$.fn.validator = function(options) {
		
		options = options || {};//The ID's instance stay in this obj
		
		//Generating ID's instance
		var data = options.data = $.data(this[0]);
		
		//Instantiating Validator
		$.validator[data] = new $.validator.init(this[0], options);
		return this;
	};
	$.extend($.expr[':'],{
		//jQuery extension
		validable:function(elm) {
			elm = elm.jquery?elm[0]:elm;
			if(!elm || !elm.type)
				return false;
			var types = {
					textarea:1,
					'select-one':1,
					'select-multiple':1,
					radio:1,
					checkbox:1,
					text:1,
					password:1,
					file:1
				},
				tags = {
					input:1,
					select:1,
					textarea:1
				};
			if(tags[elm.tagName.toLowerCase()] === 1 && types[elm.type] === 1)
				return true;
			return false;
		}
	});
	$.extend({
		validator: {
			/*
			options = {
				handler:function(msg) {this == field},
				onkeyup:true,
				nocache:true,
				onFail:function(array) {},
				alert:true,
				onSuccess:function(element) {}
			}
			*/
			init:function(form, options) {
				
				//container
				form = $(form);
				
				//Making options public
				this.options = options;
				
				//Cache the names of the object validator
				$.validator.cacheValidatorsName.call(this);
				
				var v = $.validator,
					//Parse and cache fields with respective classNames
					//If the container is a form element, the elements property of the form will be parse
					fields = v.cacheRelationship.call(this,$(':validable', form[0].tagName.toLowerCase()=='form'?form[0].elements:form));
				
				//Making fields public
				this.options.fields = fields;
					
				//'on demand' validation
				if(options.handler) {
					var validate, listener;
					
					
					//Function binded in event
					listener = function(evt) {
						//Recovering instance
						var instance = $.validator[evt.data.data];
						
						instance.options.handler.apply($(this), validate.call(instance, this));
						
					};
					
					
					//'on demand' validation only on 'blur' and 'keyup' events
					//one at once
					
					fields.bind(options.onkeyup?'keyup':'blur', this.options, listener);
					
					
					
					//keyup validations goes in just one validator (performance)
					if(options.onkeyup)
						validate = function(field) {
							//this == instance
							var _ret = v.validate.call(this, field, $.data(field,'validator')[0]);
							return _ret?[_ret]:[];
						};
					
					//Blur validation
					else
						validate = function(field) {
							var instance = this;
							return $.map($.data(field,'validator'), function(validatorName) {
								return v.validate.call(instance, field, validatorName);
							});
						};
				}
				
				//Submit function
				//You can call it when you want
				//INFO: When calling the submit function, YOU MUST SEND THE INSTANCE ID
				this.options.submit = function(evt) {
					var v = $.validator,
						data = isNaN(parseInt(evt, 10))?evt.data.data:evt,
						instance = v[data],//Recovering instance
						invalids,
						fields = instance.options.fields;
					
					//if needed re-cache of fields with respective classNames
					if(instance.options.nocache === true) {
						v.cacheValidatorsName.call(instance);
						fields = instance.options.fields = v.cacheRelationship.call(instance, $(':validable',form[0].tagName.toLowerCase()=='form'?form[0].elements:form));
					}
					
					//It's here the magic happens
					//It'll generate an array of (possible) arrays
					invalids = v.validateFields.call(instance, fields);
					
					if(invalids.length) {//When something is invalid
						
						//You can manipulate the return of submit function
						var _ret;
						
						//Your function to make the alert to user more friendly
						if(instance.options.onFail)
							_ret = instance.options.onFail(invalids);
						
						//simple alert
						else if(instance.options.alert === true)
							alert($.map(invalids,function(a) {return a.join?a.join('\n'):a;}).join('\n'));
						
						return _ret || false;
					}
					
					//Function for valids fields
					//You still can manipulate the return of submit funcion
					return options.onSuccess?
						options.onSuccess(form)
						:true;
				};
				//If you send a tag form
				//The submit function will go to the onsubmit event
				//Else you can call it when you want, for personalizations
				if(form[0].tagName.toLowerCase() == 'form')
					form.bind('submit',this.options,this.options.submit);
			},
			//Catching the name of the validators
			cacheValidatorsName:function() {
				var ret = [],
					v = $.validator;
				$.each(this.options.validators || v.validators,function(a) {ret[ret.length] = a;});
				if(this.options.validators)
					this.nameOfValidators = ret;
				else
					v.nameOfValidators = ret;
				return ret;
			},
			
			//Make all the values of the array uniques
			uniquier:function(array) {
				if(array.length <= 2) {
					if(array.length == 2 && array[0] == array[1]) {
						return [array[0]];
					}
					return array;
				}
				var unique = [],
					exist,i=0,j,len_i=array.length,len_j;
				for(;i<len_i;i++) {
					exist = false;
					len_j = unique.length;
					for(j=0;j<len_j;j++) {
						if(array[i] == unique[j]) {
							exist = true;
							break;
						}
					}
					if(!exist)
						unique[unique.length] = array[i];
				}
				return unique;
			},
			
			injectRelationship:function(field, validator, instance) {
				var data = $.data(field,'validator') || [],
					v = $.validator,
					validators = instance.options.validators || v.validators,
					flds = validators[validator]['fields'].split('|'),
					type = field.type;
				for(var i=0,len=flds.length;i<len;i++) {
					if(type == flds[i]) {
						data.push(validator);
						$.data(field,'validator',v.uniquier(data));
						return true;
					}
				}
				return false;
			},
			
			//Store the name of the validators in an array inside the .data jquery obj of the field
			//The names are concatenated, to clear the cache use the clearRelationship function
			//Return the fields that match with the validators
			cacheRelationship:function(fields) {
				
				var v = $.validator,
					
					instance = this,
				
					validators = instance.options.validators || v.validators,
					
					_ret = function() {
						
						var f = this,
							
							type = f.type,
							
							//Iterate over the names of the list
							fNames = $.map(instance.nameOfValidators || v.nameOfValidators,function(name) {
								//If the className match the validator name and
								//the field match with the fields attribute of the validator obj...
								if($.className.has(f,name)) {
									var flds = validators[name]['fields'].split('|');
									if(flds[0] == '*')
										return name;
									var i = 0,
										len = flds.length;
									while(i<len) {
										if(type == flds[i++])
											return name;
									}
								}
								return null;
								
							}),
							names = $.data(f,'validator') || [];
						
						names = v.uniquier(names.concat(fNames));
						//Caching the name of validators in jQuery cache
						$.data(f,'validator',names);
						
						//If we can validate, the field is returned
						return fNames.length?f:null;
					};
					
				
				//When a jQuery-object is passed
				//we call it again obj-per-obj
				if(fields.jquery) {
					if(fields.length > 1)
						return fields.map(_ret);
					else
						return [_ret(fields[0])];
				}
				//When you pass the field
				//It execute the function
				else
					return [_ret(fields)];
			},
			
			clearRelationship:function(elm) {
				if($.data(elm,'validator'))
					$.data(elm,'validator',[]);
				else if($.validator[$.data(elm)]) {
					var fields = $.validator[$.data(elm)].options.fields;
					for(var i=0,len=fields.length;i<len;i++)
						$.data(fields[i],'validator',[]);
				}
			},
			
			//Fields validation
			validateFields:function(fields) {
				var validate = $.validator.validate,
					instance = this;
				
				//Pass by all the fields that must be validated
				return fields.map(function() {
					
					var field = $(this),
						
						//Make the REAL validation
						valid = $.map($.data(this,'validator'),function(validatorName) {
							//validate function return an array
							//if the field is empty, an empty array will be returned
							return validate.call(instance,field,validatorName);
						});
					
					//Give the access to the field of the messages
					valid['field'] = field;
					
					//Send back the array
					//or NULL if the field is valid
					return valid.length?[valid]:null;
				}).toArray();
			},
			
			//The Engine of validation
			validate:function(field,vName) {
				//this == instance
				
				//Catching the field
				field = field['jquery']?field[0]:field;
				
				//The obj of validation
				var validator = (this.options.validators || $.validator.validators)[vName];
				
				//When invalid, the message default for the validation is returned
				//if there is a % in the message string, it will be replaced with the title attribute of the field
				if(!validator.test.call(validator,field))
					return validator.msg.replace('%','\'' + field.title + '\'');
					
				//Valid proccess
				else
					return null;
			}
		}
	});
})(jQuery);